package com.paas.service.modules.sso.cas.service.handeler;

import java.security.GeneralSecurityException;
import java.util.List;

import javax.security.auth.login.AccountNotFoundException;
import javax.validation.constraints.NotNull;

import org.jasig.cas.Message;
import org.jasig.cas.authentication.BasicCredentialMetaData;
import org.jasig.cas.authentication.Credential;
import org.jasig.cas.authentication.HandlerResult;
import org.jasig.cas.authentication.PreventedException;
import org.jasig.cas.authentication.handler.BadCredentialsAuthenticationException;
import org.jasig.cas.authentication.handler.NoOpPrincipalNameTransformer;
import org.jasig.cas.authentication.handler.PasswordEncoder;
import org.jasig.cas.authentication.handler.PlainTextPasswordEncoder;
import org.jasig.cas.authentication.handler.PrincipalNameTransformer;
import org.jasig.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
import org.jasig.cas.authentication.principal.Principal;
import org.jasig.cas.authentication.support.PasswordPolicyConfiguration;

public abstract class CusAbstractUsernamePasswordAuthenticationHandler extends
		AbstractPreAndPostProcessingAuthenticationHandler {
	/**
	 * PasswordEncoder to be used by subclasses to encode passwords for
	 * comparing against a resource.
	 */
	@NotNull
	private PasswordEncoder passwordEncoder = new PlainTextPasswordEncoder();

	@NotNull
	private PrincipalNameTransformer principalNameTransformer = new NoOpPrincipalNameTransformer();

	/** The password policy configuration to be used by extensions. */
	private PasswordPolicyConfiguration passwordPolicyConfiguration;

	/** {@inheritDoc} */
	@Override
	protected final HandlerResult doAuthentication(final Credential credential)
			throws GeneralSecurityException, PreventedException {
		final CusUsernamePasswordCredential userPass = (CusUsernamePasswordCredential) credential;
		if (userPass.getUsername() == null) {
			throw new AccountNotFoundException("Username is null.");
		}

		final String transformedUsername = this.principalNameTransformer
				.transform(userPass.getUsername());
		if (transformedUsername == null) {
			throw new AccountNotFoundException("Transformed username is null.");
		}
		userPass.setUsername(transformedUsername);
		return authenticateUsernamePasswordInternal(userPass);
	}

	/**
	 * Authenticates a username/password credential by an arbitrary strategy.
	 *
	 * @param transformedCredential
	 *            the credential object bearing the transformed username and
	 *            password.
	 *
	 * @return HandlerResult resolved from credential on authentication success
	 *         or null if no principal could be resolved from the credential.
	 *
	 * @throws GeneralSecurityException
	 *             On authentication failure.
	 * @throws PreventedException
	 *             On the indeterminate case when authentication is prevented.
	 * @throws BadCredentialsAuthenticationException 
	 */
	protected abstract HandlerResult authenticateUsernamePasswordInternal(
			final CusUsernamePasswordCredential transformedCredential)
			throws GeneralSecurityException, PreventedException;

	/**
	 * Method to return the PasswordEncoder to be used to encode passwords.
	 *
	 * @return the PasswordEncoder associated with this class.
	 */
	protected final PasswordEncoder getPasswordEncoder() {
		return this.passwordEncoder;
	}

	protected final PrincipalNameTransformer getPrincipalNameTransformer() {
		return this.principalNameTransformer;
	}

	protected final PasswordPolicyConfiguration getPasswordPolicyConfiguration() {
		return this.passwordPolicyConfiguration;
	}

	/**
	 * Helper method to construct a handler result on successful authentication
	 * events.
	 *
	 * @param credential
	 *            the credential on which the authentication was successfully
	 *            performed. Note that this credential instance may be different
	 *            from what was originally provided as transformation of the
	 *            username may have occurred, if one is in fact defined.
	 * @param principal
	 *            the resolved principal
	 * @param warnings
	 *            the warnings
	 * @return the constructed handler result
	 */
	protected final HandlerResult createHandlerResult(
			final Credential credential, final Principal principal,
			final List<Message> warnings) {
		return new HandlerResult(this, new BasicCredentialMetaData(credential),
				principal, warnings);
	}

	/**
	 * Sets the PasswordEncoder to be used with this class.
	 *
	 * @param passwordEncoder
	 *            the PasswordEncoder to use when encoding passwords.
	 */
	public final void setPasswordEncoder(final PasswordEncoder passwordEncoder) {
		this.passwordEncoder = passwordEncoder;
	}

	public final void setPrincipalNameTransformer(
			final PrincipalNameTransformer principalNameTransformer) {
		this.principalNameTransformer = principalNameTransformer;
	}

	public final void setPasswordPolicyConfiguration(
			final PasswordPolicyConfiguration passwordPolicyConfiguration) {
		this.passwordPolicyConfiguration = passwordPolicyConfiguration;
	}

	/**
	 * @return True if credential is a {@link CusUsernamePasswordCredential}, false
	 *         otherwise.
	 */
	public boolean supports(final Credential credential) {
		return credential instanceof CusUsernamePasswordCredential;
	}
}
